package com.modernyuanshiren.withwinds;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.modernyuanshiren.GameMap;
import com.modernyuanshiren.IElement;
import com.modernyuanshiren.IPosition;
import com.modernyuanshiren.ISingleAction;
import com.modernyuanshiren.Position;

public enum WindSingleAction implements
		ISingleAction<WindStatus, WindSingleAction> {
	MOVE_LEFT("左") {
		@Override
		public void apply(WindStatus status, IElement... elements) {
			apply(-1, status, elements);
		}

		@Override
		public boolean acceptable(WindStatus status, IElement... elements) {
			return acceptable(-1, status, elements);
		}
	},

	MOVE_RIGHT("右") {
		@Override
		public void apply(WindStatus status, IElement... elements) {
			apply(1, status, elements);
		}

		@Override
		public boolean acceptable(WindStatus status, IElement... elements) {
			return acceptable(1, status, elements);
		}
	},

	MOVE_UP("上") {
		@Override
		public void apply(WindStatus status, IElement... elements) {
			apply(0, -1, status, elements);
		}

		@Override
		public boolean acceptable(WindStatus status, IElement... elements) {
			return acceptable(0, -1, status, elements);
		}
	},

	MOVE_DOWN("下") {
		@Override
		public void apply(WindStatus status, IElement... elements) {
			apply(0, 1, status, elements);
		}

		@Override
		public boolean acceptable(WindStatus status, IElement... elements) {
			return acceptable(0, 1, status, elements);
		}
	},
	;
	private String name;

	private WindSingleAction(String direction) {
		this.name = String.format("向%s走1格", direction);
	}

	@Override
	public String getName() {
		return name;
	}

	protected void apply(int dx, WindStatus status, IElement... elements) {
		apply(dx, 0, status, elements);
	}

	protected void apply(int dx, int dy, WindStatus status,
			IElement... elements) {
		this.translate(elements[0], dx, dy);
		List<IElement> moves = Stream.of(elements).skip(1).distinct()
				.filter(e -> canMove(e, dx, dy, status, elements)).collect(Collectors.toList());
		moves.forEach(e -> this.translate(e, dx, dy));
	}

	private boolean canMove(IElement e, int dx, int dy, WindStatus status, IElement... elements) {
		CommonWindGame gameContext = CommonWindGame.getGameContext();
		GameMap map = gameContext.getMap();

		IPosition target = getTargetPosition(e, dx, dy);
		if (map.isOneOf(target, gameContext.getWallName(),
				gameContext.getTargetName())) {
			return false;
		}
		
		boolean blocked1 = hasBox(target, status) && map.isNormalFloor(target);
		boolean blocked2 = isSafeHole(target, status) && status.getBoxCountAt(target) > 1;
		if (blocked1 || blocked2) {
			Optional<IElement> blocked = Stream.of(elements).filter(el->el.getPosition().equals(target)).findAny();
			if(blocked.isPresent()){
				return canMove(blocked.get(), dx,dy,status, elements);
			}
			return false;
		}

		return true;
	}

	protected boolean acceptable(int dx, WindStatus status,
			IElement... elements) {
		return acceptable(dx, 0, status, elements);
	}

	protected boolean acceptable(int dx, int dy, WindStatus status,
			IElement... elements) {
		CommonWindGame gameContext = CommonWindGame.getGameContext();
		if (!elements[0].is(gameContext.getCharacterName())) {
			return false;
		}
		GameMap map = gameContext.getMap();
		IElement man = elements[0];
		IPosition target = getTargetPosition(man, dx, dy);
		boolean canmove;
		if (hasBox(target, status) && map.isNormalFloor(target)) {
			canmove = false;
		} else {
			canmove = map.isNormalFloor(target)
					|| map.is(target, gameContext.getTargetName())
					|| (isSafeHole(target, status) && status.getBoxCountAt(target)==1);
		}
		if(elements.length==1) {
			return canmove;
		}

		boolean allFilled = gameContext.getMap()
				.getAllPositions(gameContext.getTargetHoleName()).stream()
				.allMatch(pos -> hasBox(pos, status));
		boolean gotoTargetHole = Stream
				.of(elements)
				.skip(1)
				.map(e -> getTargetPosition(e, dx, dy))
				.anyMatch(
						p -> map.is(p, gameContext.getTargetHoleName())
								&& (status.getBoxCountAt(p) < 2));
		boolean block = allFilled && gotoTargetHole;

		long liveBoxes = elements.length - 1;
		long willDie = Stream.of(elements).skip(1)
				.map(e -> getTargetPosition(e, dx, dy))
				.filter(pos -> willDie(pos, status)).count();
		boolean boxSafe = liveBoxes - willDie >= status.getEmptyTargetCount();
		return canmove && (!block) && boxSafe;
	}

	private Position getTargetPosition(IElement element, int dx, int dy) {
		return new Position(element.getPosition().getX() + dx, element
				.getPosition().getY() + dy);
	}

	private boolean willDie(IPosition pos, WindStatus status) {
		CommonWindGame gameContext = CommonWindGame.getGameContext();
		GameMap map = gameContext.getMap();
		return map.is(pos, gameContext.getDangerName())
				|| (map.is(pos, gameContext.getNotTargetHoleName()) && !hasBox(
						pos, status));
	}

	private boolean hasBox(IPosition target, WindStatus status) {
		return status.getBoxCountAt(target)>0;
	}

	private boolean isSafeHole(IPosition pos, WindStatus status) {
		CommonWindGame gameContext = CommonWindGame.getGameContext();
		GameMap map = gameContext.getMap();
		boolean isHole = map.isOneOf(pos, gameContext.getTargetHoleName(),
				gameContext.getNotTargetHoleName());
		return isHole && hasBox(pos, status);
	}

	@Override
	public boolean notNecessary(WindSingleAction preAction) {
		return false;
	}
}
